package org.codefinger.json.util;

@SuppressWarnings("unchecked")
public class IdentityCache<K, V> {

	private static final int	DEFAULT_INITIAL_CAPACITY	= 1024;

	private Entry<K, V>[]		buckets;

	private int					indexMask;

	private int					threshold;

	private int					size;

	private ValueBuilder<K, V>	builder;

	public IdentityCache(ValueBuilder<K, V> builder) {
		this(DEFAULT_INITIAL_CAPACITY, builder);
	}

	public IdentityCache(int tableSize, ValueBuilder<K, V> builder) {
		this.buckets = new Entry[tableSize];
		this.builder = builder;
		this.indexMask = tableSize - 1;
		this.threshold = (int) (tableSize * 0.75f);
	}

	public V get(K key) {
		int hash = System.identityHashCode(key);

		for (Entry<K, V> entry = buckets[hash & indexMask]; entry != null; entry = entry.next) {
			if (key == entry.key) {
				return entry.value;
			}
		}

		V value = builder.build(key);
		put(key, value);

		return value;
	}

	public synchronized boolean put(K key, V value) {
		int hash = System.identityHashCode(key);
		int bucket = hash & indexMask;

		for (Entry<K, V> entry = buckets[bucket]; entry != null; entry = entry.next) {
			if (key == entry.key) {
				entry.value = value;
				return true;
			}
		}

		buckets[bucket] = new Entry<K, V>(key, value, hash, buckets[bucket]);
		if (++size > threshold) {
			resize();
		}
		return false;
	}

	private void resize() {
		int tableSize = buckets.length << 1;
		Entry<K, V>[] newBuckets = new Entry[tableSize];
		int indexMask = tableSize - 1;
		int hash;
		int bucket;
		for (Entry<K, V> entry : buckets) {
			while (entry != null) {
				hash = entry.hashCode;
				bucket = hash & indexMask;
				newBuckets[bucket] = new Entry<K, V>(entry.key, entry.value, hash, newBuckets[bucket]);
				entry = entry.next;
			}
		}
		this.indexMask = indexMask;
		this.buckets = newBuckets;
		this.threshold = threshold << 1;
	}

	private static final class Entry<K, V> {

		public final int			hashCode;

		public final K				key;

		public V					value;

		public final Entry<K, V>	next;

		public Entry(K key, V value, int hash, Entry<K, V> next) {
			this.key = key;
			this.value = value;
			this.next = next;
			this.hashCode = hash;
		}

	}

	public static interface ValueBuilder<K, V> {

		public V build(K key);

	}

}
